package org.redmagic.agent.spring;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.redmagic.utils.Conditions;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;

/**
 * Extension of Spring's {@link BeanDefinitionVisitor} class that allows
 * a user to scan through all the BeanDefinition's first to collect
 * all the present placeholders.  The reason this could be useful, is if
 * you want to make a call to an external system (ex. a web service), it may be more
 * efficient to make one call with all the placeholders, than many individual
 * calls for each placeholder.
 */
public class AggregatingBeanDefinitionVisitor extends BeanDefinitionVisitor {

	private final DefaultPropertyPlaceholderHelper helper;
    private final Set<BeanDefinition> beanDefinitionsWithPlaceholders;
    private final Set<String> placeholders;
    
    private BeanDefinition parentBeanDefinition;
    private boolean discoverMode = true;
    private Properties resolvedProperties;
    
    public AggregatingBeanDefinitionVisitor() {
        super();
        this.helper = new DefaultPropertyPlaceholderHelper();
        this.placeholders = new HashSet<String>();
        this.beanDefinitionsWithPlaceholders = new HashSet<BeanDefinition>();
        this.resolvedProperties = new Properties();
    }

    /**
     * This method is used to scan the given BeanDefinition for placeholders.  If the
     * bean contains any placeholders it is added to the list of beans to be resolved
     * later, and it's placeholders are added to the list of placeholders that need to
     * be resolved.
     */
    public void collectBeanDefinition(BeanDefinition beanDefinition) {
        Conditions.notNull(beanDefinition, "beanDefinition");
        this.discoverMode = true;
        this.parentBeanDefinition = beanDefinition;
        this.visitBeanDefinition(beanDefinition);
    }

    /**
     * The properties object passed in is used to replace the placeholders in the
     * beans that were collected using "collectBeanDefinition".
     */
    public void replacePlaceholders(Properties resolvedProperties) {
        Conditions.notNull(resolvedProperties, "resolvedProperties");
        this.discoverMode = false;
        this.resolvedProperties = new Properties();
        this.resolvedProperties.putAll(resolvedProperties);
        for (BeanDefinition beanDefinition : beanDefinitionsWithPlaceholders) {
            this.visitBeanDefinition(beanDefinition);
        }
    }
    
    /**
     * When "collecting" beans, this method causes the provided bean to be added
     * to the list of beans that needed to be resolved later.  When in "replacing" mode,
     * this method will used the passed in properties to resolve the given placeholder.
     */
    @Override
    protected String resolveStringValue(String placeholder) {
        if (discoverMode) {
            return discoverPlaceholders(placeholder);
        } else {
            return this.helper.replacePlaceholders(placeholder, resolvedProperties);
        }
    }

    private String discoverPlaceholders(String inputString) {
        Set<String> extractedPlaceholders = this.helper.extractPlaceholders(inputString);
        if (!extractedPlaceholders.isEmpty()) {
            this.beanDefinitionsWithPlaceholders.add(parentBeanDefinition);
            this.getPlaceholders().addAll(extractedPlaceholders);
        }
        return inputString;
    }

    
    public void reset() {
        this.placeholders.clear();
        this.discoverMode = true;
        this.beanDefinitionsWithPlaceholders.clear();
        this.resolvedProperties.clear();
    }
    
    public Set<String> getPlaceholders() {
        return placeholders;
    }
}